package net.sf.rose.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Hashtable;

/**
 * A class loader is an object that is responsible for loading classes. Given
 * the binary name of a class, a class loader should attempt to locate or
 * generate data that constitutes a definition for the class. A typical strategy
 * is to transform the name into a file name and then read a "class file" of
 * that name from a file system.
 * @version 2.0, 12/01/10
 * @author feng jian
 * 
 */
public class DynamicClassLoader extends ClassLoader {

	// a classpath for search
	private File classpath = new File("");
	
    // The parent class loader for delegation
    private ClassLoader parent = null;

	// hashtable that memory the loaded classes
	private static Hashtable<String, Class<?>> loadClassHashTable = new Hashtable<String, Class<?>>();

	/**
	 * 
	 */
	public DynamicClassLoader() {
		super();
	}

	/**
	 * 
	 */
	public DynamicClassLoader(File classpath) {
		this();
		this.classpath = classpath;
	}

	/**
	 * set the classpath
	 * @param myClasspath the specified classpath name
	 */
	public void setClasspath(File classpath) {
		this.classpath = classpath;
	}

	/**
	 * set the ClassLoader
	 * @param parent the specified ClassLoader
	 */
	public void setParent(ClassLoader parent) {
		this.parent = parent;
	}

	/**
	 * set the classpath
	 */
	public void clearLoadClass() {
		loadClassHashTable.clear();
	}

	/**
	 * set the classpath
	 */
	public Class<?> removeLoadClass(String className) {
		return loadClassHashTable.remove(className);
	}

	/**
	 * Loads the class with the specified binary name. This method searches for
	 * classes in the same manner as the loadClass(String, boolean) method.
	 * Invoking this method is equivalent to invoking {loadClass(name,false)}.
	 * @param className The binary name of the class.
	 * @return The resulting <TT>Class</TT> object.
	 * @throws ClassNotFoundException If the class was not found.
	 */
	public Class<?> loadClass(String className) throws ClassNotFoundException {
		return loadClass(className, false);
	}

	/**
	 * 这个方法覆盖了父类的该方法,目的是要按照我们自定的加载顺序进行加载.
	 * Loads the class with the specified binary name. The default
	 * implementation of this method searches for classes in the following
	 * order:
	 * Invoke {findLoadedClass(String)} to check if the class has already been loaded.
	 * Invoke {findSystemClass(String)} to load the system class.
	 * Invoke the {findClass(String)} method to find the class.
	 * If the class was found using the above steps, and the resolve flag is
	 * true, this method will then invoke the {resolveClass(Class)} method on
	 * the resulting Class object.
	 * @param name The binary name of the class.
	 * @param resolve If true then resolve the class.
	 * @return The resulting Class object.
	 * @throws ClassNotFoundException If the class could not be found.
	 */
	protected Class<?> loadClass(String name, boolean resolve)
			throws ClassNotFoundException {
		// 我们要创建的Class对象
		Class<?> foundClass = null;

		// 必需的步骤1：如果类已经在系统缓冲之中，我们不必再次装入它
		foundClass = findLoadedClass(name);
		if (foundClass != null) {
			return foundClass;
		}

		// 下面是定制部分
		// if the class is systemClass, load the system class by system
		if (name.startsWith("java.")) {
			foundClass = findSystemClass(name);
			loadClassHashTable.put(name, foundClass);
			return foundClass;
		}

		// 如果类已经在本地缓存之中，我们不必再次装入它
		foundClass = loadClassHashTable.get(name);
		if (foundClass != null) {
			return foundClass;
		}

		if (parent != null) {
			try{
				foundClass = parent.loadClass(name);
				return foundClass;
			}catch(ClassNotFoundException e){
			}
		}

		// invoke the findClass() method to load the class
		foundClass = findClass(name);

		if (resolve && (foundClass != null)) {
			resolveClass(foundClass);
		}
		return foundClass;
	}

	/**
	 * Finds the class with the specified binary name.The default implementation
	 * throws a ClassNotFoundException.
	 * @param className The binary name of the class.
	 * @return The resulting Class object.
	 * @throws ClassNotFoundException If the class could not be found.
	 */
	public Class<?> findClass(String className) 
			throws ClassNotFoundException {
		// 通过某种方法获取字节码数据 ,这个就是重要的要求能够加密解密的地方.
		byte[] classData = null;

		// loadClassData 负责根据class文件名字找到该类的class文件,并且返回字节数组.
		classData = loadClassData(className);

		if (classData == null) {
			return null;
		}
		Class<?> c = defineClass(className, classData, 0, classData.length);
		loadClassHashTable.put(className, c);
		return c;
	}

	/**
	 * Loads the classData with the specified binary name. This method searches
	 * for classes in the specified classpath as
	 * searchFile(myClasspath,className) method.
	 * @param name The binary name of the class
	 * @return The resulting the classData of the class object by byte[]
	 * @throws IOException if have some failed or interrupted I/O operations.
	 */
	private byte[] loadClassData(String className)
			throws ClassNotFoundException {
		File file = null;
		if(className.indexOf('.') == -1){
			file = new File(classpath, className + ".class");
		}else{
			int i = className.lastIndexOf('.');
			String path = className.substring(0, i).replace('.', '/');
			String name = className.substring(i + 1, className.length());
			file = new File(classpath, path);
			file = new File(file, name + ".class");
		}
		try{
			if (file.exists()) {
				FileInputStream inFile = new FileInputStream(file);
				byte[] classData = new byte[inFile.available()];
				inFile.read(classData);
				inFile.close();
				return classData;
			}else{
				String message = "can't find file name:" + file.getAbsolutePath();
				throw new ClassNotFoundException(message);
			}
		}catch (IOException e) {
			ClassNotFoundException r = new ClassNotFoundException(e.getMessage());
			r.setStackTrace(e.getStackTrace());
			throw r;
		}
	}

	/**
	 * Loads the class with the specified binary name.The default implementation
	 * throws a ClassNotFoundException.
	 * @param classData The data of the class.
	 * @param className The binary name of the class.
	 * @return The resulting Class object.
	 * @throws ClassNotFoundException If the class could not be found.
	 */
	public Class<?> loadClass(byte[] classData, String className)
			throws ClassNotFoundException {
		// System.out.println("MyClassLoader is loading : " + className + "");
		Class<?> c = defineClass(className, classData, 0, classData.length);
		loadClassHashTable.put(className, c);
		// System.out.println("Complete to load the class :" + className);
		return c;
	}

	public Class<?> reload(String fileName) throws ClassNotFoundException {
		DynamicClassLoader mc2 = new DynamicClassLoader(classpath);
		return mc2.loadClass(fileName);
	}
}
